// =====================================================================================
// 
//       Filename:  9gigafs.c
// 
//    Description:  FUSE filesystem to connect to a 9 Giga personal space
// 
//        Version:  1.0
//        Created:  20/10/2010 22:02:33
//       Revision:  none
//       Compiler:  gcc
// 
//         Author:  François Hissel (fh), francois.hissel@m4x.org
//        Company:  
// 
// =====================================================================================

#define	FUSE_USE_VERSION 26			// FUSE version

#include	<stdlib.h>
#include	<fuse.h>
#include	<stdio.h>
#include	<string.h>
#include	<stddef.h>
#include	<errno.h>
#include	<fcntl.h>
#include	<unistd.h>
#include	<sys/types.h>
#include	<pthread.h>
#include	<fuse_opt.h>
#include	"protocol.h"
#include	"filecaching.h"

#define NGFS_OPT_KEY(t,u,p) { t ,offsetof(struct options, p ), 1 } , { u ,offsetof(struct options, p ), 1 }
#define NGFS_OPT_KEY2(t,u,p,v) { t ,offsetof(struct options, p ), v } , { u ,offsetof(struct options, p ), v }
#define	CACHE_MAX_SIZE 10000000			//!< Maximum size of cache

/**
 * \brief Options parsed from the command line
 *
 * This structure holds all the information that has been parsed from the command line, and will be used later for connection to the remote server.
 */
struct options {
	char* username;	//!< User name
	char* password;	//!< Password
} options;

FileList *cache;	//!< Cache of files. Each file opened on the virtual filesystem is associated with a real temporary file. This variable holds the association between the files
pthread_mutex_t cache_lock=PTHREAD_MUTEX_INITIALIZER;	//!< Mutex to protect concurrent accesses on the cache
size_t cache_size;	//!< Total size of cache
uid_t uid;	//!< Current user ID
gid_t gid;	//!< Current group ID

/**
 * \brief Recursive part of the cleanup_cache function
 *
 * The cleanup_cache function is a recursive function called on the root of the Folder structure, and then for each of its children. This is the recursive part of the function.
 * \param folder Base folder from which the local files will be deleted
 */
void cleanup_cache_rec(Folder *folder) {
	if (folder->file->type==T_FILE && folder->file->id!=0) {
		char name[200];
		get_local_file_name(folder->file->id,name);
		unlink(name);
		folder->file->id=0;
	} else {
		Folder *f=folder->elements;
		while (f!=0) {
			cleanup_cache_rec(f);
			f=f->next;
		}
	}
}

/**
 * \brief Second recursive part of the cleanup_cache function
 *
 * The cleanup_cache function is a recursive function called on the root of the Folder structure. The first part of it removes all files from the disk. The second part, this one, also removes the FileHandle structures from the cache, only if the corresponding file is not opened (that is if it has a non-null handle).
 */
void cleanup_cache_rec2(FileList **fl) {
	FileList *f=*fl;
	FileList *next;
	while (f!=0 && f->file.handle!=0) {
		next=f->next;
		f->next=0;
		free_file_list(f);
		f=next;
	}
	*fl=f;
	if (f==0) return;
	while (f->next!=0) {
		if (f->next->file.handle!=0) {
			next=f->next->next;
			f->next->next=0;
			free_file_list(f->next);
			f->next=next;
		} else f=f->next;
	}
}

/**
 * \brief Erase the cache of files
 *
 * This function deletes the whole cache of files. It actually removes the files from the disk, and thus may result in less good performances of IO access later. It is automatically called when the cache reaches a maximum size
 */
void cleanup_cache() {
	pthread_mutex_lock(&cache_lock);
	cleanup_cache_rec(persistent.tree);
	cleanup_cache_rec2(&cache);
	pthread_mutex_unlock(&cache_lock);
}

/**
 * \brief Initialize the filesystem
 *
 * This function does all the technical stuff which has to be done before the start of the program. In particular, it allocates storage for the structures in memory (especially the cache of the filesystem), and trys to connect to the remote server by providing the user ID and password. The fuse_conn_info structure tells which capabilities FUSE provides and which one are needed and activated by the client. The function returns a pointer which will be available in all file operations later.
 * \param conn Capabilities requested by the application
 * \return Pointer to a persistent structure, available in future file operations
 */
void *ngfs_init(struct fuse_conn_info *conn) {
#ifdef TRACE
	fprintf(stderr,"ngfs_init\n");
#endif
	// Find out if a proxy is configured and use it
	char *prox=getenv("http_proxy");
	if (prox!=0) {
		if (strncmp(prox,"http",4)==0) strncpy(proxy,prox+7,STRING_SIZE-1);
		else strncpy(proxy,prox,STRING_SIZE-1);
	}
	prox=strchr(proxy,':');
	if (prox==0) strcpy(proxy_port,HTTP_PORT);
	else {
		*prox++=0;
		strcpy(proxy_port,prox);
	}
	// Allocate memory for persistent structures
	init_resources();
	// Setup connection
	conn->async_read=0;
	conn->want=0;
	// Connect to the remote website
	if (connect_service("9giga.sfr.fr","80",options.username,options.password)!=0) {
		fuse_exit(fuse_get_context()->fuse);
	}
	free(options.username);
	free(options.password);
	// Initialize cache of files
	cache=0;
	cache_size=0;
	return 0;
}

/**
 * \brief Unmount the filesystem
 *
 * This function is called when the filesystem is unmounted. It releases all allocated memory.
 * \param private_data Private data initialized and returned by function ngfs_init
 */
void ngfs_destroy(void *private_data) {
#ifdef TRACE
	fprintf(stderr,"ngfs_destroy\n");
#endif
	free_resources();
	free_file_list(cache);
}

/**
 * \brief Retrieve the attributes of a file on the virtual filesystem
 *
 * This function loads the attributes of a chosen file and stores it in the stbuf variable. It is used in nearly every file access, so it should be really well designed. It is a common implementation for ngfs_getattr and ngfs_fgetattr.
 * \param fold Pointer to the Folder structure associated with the file in memory
 * \param stbuf Structure in which the attributes will be stored
 * \return Error code, 0 if everything went fine
 */
int common_getattr(Folder *fold,struct stat *stbuf) {
	memset(stbuf,0,sizeof(struct stat));
	int nbdir=0;
	if (fold->file->type==T_FOLDER) {
		stbuf->st_mode=S_IFDIR | 0777;
		Folder *children=fold->elements;
		while (children!=0) {
			if (children->file->type==T_FOLDER) ++nbdir;
			children=children->next;
		}
		stbuf->st_nlink=nbdir+2;
	} else {
		stbuf->st_mode=S_IFREG | 0666;
		stbuf->st_nlink=1;
	}
	stbuf->st_size=fold->file->size;
	stbuf->st_mtime=fold->file->date;
	stbuf->st_uid=uid;
	stbuf->st_gid=gid;
	return 0;
}

/**
 * \brief Get the attributes of a file on the virtual filesystem
 *
 * This function loads the attributes of a chosen file and stores it in the stbuf variable. It is used if no file handle is provided through a fuse_file_info structure.
 * \param path Virtual path of the file on the remote file system, which is usually not the path on the web server
 * \param stbuf Structure in which the attributes will be stored
 * \return Error code, 0 if everything went fine
 */
int ngfs_getattr(const char *path,struct stat *stbuf) {
#ifdef TRACE
	fprintf(stderr,"ngfs_getattr(%s)\n",path);
#endif
	pthread_mutex_lock(&cache_lock);
	FileList *fl=find_name(cache,path);
	int code;
	if (fl!=0 && fl->file.handle!=0) {	// If the file is in cache and is a real file (and not a folder), get the stats from the local file associated
		if (fl->file.handle!=0) code=fstat(fl->file.handle,stbuf); else {
			char name[200];
			get_local_file_name(fl->file.id,name);
			code=stat(name,stbuf);
		}
		pthread_mutex_unlock(&cache_lock);
		if (code!=0) return -errno; else return 0;
	} else {	// If not, get the stats from the folder tree
		Folder *fold;
		code=get_node(path,&fold);
		pthread_mutex_unlock(&cache_lock);
		if (code!=0) return -ENOENT;
		return common_getattr(fold,stbuf);
	}
}

/**
 * \brief Get the attributes of a file on the virtual filesystem
 *
 * This function loads the attributes of a chosen file and stores it in the stbuf variable. It is used if a file handle is provided through a fuse_file_info structure.
 * \param path Virtual path of the file on the remote file system, which is usually not the path on the web server. It is not used if a file handle is provided
 * \param stbuf Structure in which the attributes will be stored
 * \param fi FUSE file information structure 
 * \return Error code, 0 if everything went fine
 */
int ngfs_fgetattr(const char *path,struct stat *stbuf,struct fuse_file_info *fi) {
#ifdef TRACE
	fprintf(stderr,"ngfs_fgetattr(%s,%p)\n",path,(void*)(long)(fi->fh));
	print_file_list(stderr,cache);
#endif
	if (fi==0 || fi->fh==0) return -EBADF;
	pthread_mutex_lock(&cache_lock);
	FileList *fl=(FileList*)(long)(fi->fh);
	//if (fl->file.localpath==0) return -ENOENT;
	if (fl->file.folder==0) {	// The element in the cache is a real file, and not a folder. Get the stats of the local file associated with it
		int code;
		if (fl->file.handle!=0) code=fstat(fl->file.handle,stbuf); else {
			char name[200];
			get_local_file_name(fl->file.id,name);
			code=stat(name,stbuf);
		}
		pthread_mutex_unlock(&cache_lock);
		if (code!=0) return -errno; else return 0;
	} else {	// The element is a folder, get the stats from the folder tree
		Folder *fold=fl->file.folder;
		pthread_mutex_unlock(&cache_lock);
		return common_getattr(fold,stbuf);
	}
}

/**
 * \brief Check if a file can be accessed to with the given rights
 *
 * This function checks if the file at the path address (virtual path) can be accessed to. It returns 0 if the file exists and the user is authorized to access to it, and another value otherwise.
 * \param path Virtual path of the file
 * \param mask Required permissions mask
 * \return 0 if the file can be opened, another value otherwise
 */
int ngfs_access(const char *path,int mask) {
#ifdef TRACE
	fprintf(stderr,"ngfs_access(%s,%o)\n",path,mask);
#endif
	int is_folder;
	pthread_mutex_lock(&cache_lock);
	FileList *fl=find_name(cache,path);
	if (fl!=0) is_folder=(fl->file.handle==0);
	pthread_mutex_unlock(&cache_lock);
	if (fl!=0) {
		if ((mask & X_OK)!=0 && is_folder) return -EACCES;
	} else {
		Folder *fold;
		int code=get_node(path,&fold);
		if (code!=0 || fold==0) return -ENOENT;
		if ((mask & X_OK)!=0 && fold->file->type!=T_FOLDER) return -EACCES;
	}
	return 0;
}

/**
 * \brief Open a directory for reading
 *
 * This function is called to open a directory. It actually looks if the directory has already been loaded, and if not, loads it in the memory. The file handle (address of the Folder structure in memory) is written in the fuse_file_info structure.
 * \param path Virtual path of the directory
 * \param fi FUSE information on the directory, which contains the file handle
 * \return Error code of the function, 0 if everything went fine
 */
int ngfs_opendir(const char *path,struct fuse_file_info *fi) {
#ifdef TRACE
	fprintf(stderr,"ngfs_opendir(%s,%p)\n",path,(void*)(long)(fi->fh));
#endif
	Folder *fold;
	if (get_node(path,&fold)!=0) return -1;
	pthread_mutex_lock(&cache_lock);
	FileList *fl=create_file_list(0,path,fold);
	fl->next=cache;
	cache=fl;
	fi->fh=(long)(fl);
	pthread_mutex_unlock(&cache_lock);
	return 0;
}

/**
 * \brief Release a directory structure
 *
 * This function is called when the user does not need to read a directory any longer. It is the equivalent of the release function for directories. The function releases the directory handle and frees all memory allocated to store the directoy element. The actual contents of the virtual directory is kept in memory for future usage.
 * \param path Directory which will be released, this variable is not used
 * \param fi FUSE information on the directory
 * \return Error code, or 0 if everything went fine
 */
int ngfs_releasedir(const char *path,struct fuse_file_info *fi) {
#ifdef TRACE
	fprintf(stderr,"ngfs_releasedir(%s,%p)\n",path,(void*)(long)(fi->fh));
	print_file_list(stderr,cache);
#endif
	if (fi==0 || fi->fh==0) return -EBADF;
	pthread_mutex_lock(&cache_lock);
	int code=free_handle(&cache,(FileList*)(long)(fi->fh));
	pthread_mutex_unlock(&cache_lock);
	if (code!=0) return -EBADF;
	return 0;
}

/**
 * \brief Read the content of a directory
 *
 * This function returns all directory entries to the caller. It is one of the main functions of the filesystem.
 * \param path Virtual path of the directory
 * \param buf Name of a file in the directory
 * \param filler Filler function provided by the FUSE system
 * \param offset Offset of the next directory entry, in the case files are provided one by one
 * \param fi File information structure
 * \return 0 if there are no more files or if the filler function returned a non-null value, another value otherwise
 */
int ngfs_readdir(const char *path,void *buf,fuse_fill_dir_t filler,off_t offset,struct fuse_file_info *fi) {
#ifdef TRACE
	fprintf(stderr,"ngfs_readdir(%s,%p)\n",path,(fi==0)?0:(void*)(long)(fi->fh));
	print_file_list(stderr,cache);
#endif
	if (fi==0 || fi->fh==0) return -EBADF;
	pthread_mutex_lock(&cache_lock);
	FileList *fl=(FileList*)(long)(fi->fh);
	if (fl==0) {
		pthread_mutex_unlock(&cache_lock);
		return -EBADF;
	}
	if (fl->file.handle!=0) {
		pthread_mutex_unlock(&cache_lock);
		return -ENOTDIR;
	}
	Folder *fold=fl->file.folder;
	fold=fold->elements;
	filler(buf,".",0,0);
	filler(buf,"..",0,0);
	while (fold!=0) {
		filler(buf,fold->file->name,0,0);
		fold=fold->next;
	}
	pthread_mutex_unlock(&cache_lock);
	return 0;
}

/**
 * \brief Make a new directory
 *
 * This function creates a new empty directory on the remote server.
 * \param path Virtual path of the new directory
 * \param mode Directory permissions. Since permissions are not managed on the remote server, this variable is not used
 * \return Error code, or 0 if everything went fine
 */
int ngfs_mkdir(const char *path,mode_t mode) {
#ifdef TRACE
	fprintf(stderr,"ngfs_mkdir(%s)\n",path);
#endif
	return make_dir(path);
}

/**
 * \brief Delete a file
 *
 * This function removes an file from the remote disk.
 * \param path Virtual path of the element
 * \return Error code, or 0 if everything went fine
 */
int ngfs_unlink(const char *path) {
#ifdef TRACE
	fprintf(stderr,"ngfs_unlink(%s)\n",path);
#endif
	return delete_file(path);
}

/**
 * \brief Delete a directory
 *
 * This function removes a directory from the remote disk, if it is empty
 * \param path Virtual path of the directory
 * \return Error code, or 0 if everything went fine
 */
int ngfs_rmdir(const char *path) {
#ifdef TRACE
	fprintf(stderr,"ngfs_rmdir(%s)\n",path);
#endif
	Folder *fold;
	int code=get_node(path,&fold);
	if (code==0 || fold==0) return -ENOENT;
	if (fold->file->type==T_FOLDER && fold->elements!=0) return -ENOTEMPTY;
	return delete_file(path);
}

/**
 * \brief Rename or move a file or a folder
 *
 * This function changes the name of a file or a folder on the remote disk, or moves it between two folders.
 * \param from Old path and name of the element
 * \param to New path and name of the element
 * \return Error code, or 0 if everything went fine
 */
int ngfs_rename(const char *from,const char *to) {
#ifdef TRACE
	fprintf(stderr,"ngfs_rename(%s,%s)\n",from,to);
#endif
	return move_file(from,to);
}

/**
 * \brief Truncate a file to a given size
 *
 * This function truncates the file to the given size. Its implementation is required for a writable filesystem, since it is used everytime a file should be recreated.
 * \param path Virtual path of the file on the remote system
 * \param size New size of the file, in bytes
 * \return Error code, or 0 if everything went fine
 */
int ngfs_truncate(const char *path,off_t size) {
#ifdef TRACE
	fprintf(stderr,"ngfs_truncate(%s,%i)\n",path,(int)size);
#endif
	int code;
	char pathname[PATH_SIZE];
	char filename[PATH_SIZE];
	get_path_and_name(path,pathname,filename);
	if (size==0) {	// If the new size is null, don't download the file. This case may happen quite often
		code=delete_file(path);
		if (code==1) return -ENOENT; else if (code==-1) return -1;
		code=upload_file(pathname,filename,"/dev/null");
		if (code!=0) return -1;
	} else {	// Download the file and truncate it before uploading the new file to the server
		size_t l=strlen(P_tmpdir);
		char localname[l+20];
		strcpy(localname,P_tmpdir);
		localname[l]='/';
		strcpy(localname+l+1,"tmp");
		code=download_file(path,localname);
		if (code==1) return -ENOENT; else if (code==-1) return -1;
		code=truncate(localname,size);
		if (code!=0) return -errno;
		upload_file(pathname,filename,localname);
		if (code!=0) return -1;
	}
	return 0;
}

/**
 * \brief Truncate a file, used when called through a ftruncate call of the filesystem
 *
 * This function truncates a file to exactly size bytes, exactly as the truncate function. But this one is used when the filesystem is issued a ftruncate call. Basically, the only difference is that it uses the file handle if available.
 */
int ngfs_ftruncate(const char *path,off_t size,struct fuse_file_info *fi) {
#ifdef TRACE
	fprintf(stderr,"ngfs_ftruncate(%s,%i,%p)\n",path,(int)size,(void*)(long)(fi->fh));
#endif
	if (fi==0 || fi->fh==0) return -EBADF;
	FileList *fl=(FileList*)(long)(fi->fh);
	if (fl->file.handle==0) return -EISDIR;
	if (ftruncate(fl->file.handle,size)!=0) return -errno;
	return 0;
}

/**
 * \brief Open a file
 *
 * This function opens a file. It first locates it in the remote file system and loads its attributes. Then, since it is not possible to modify a file online, the full content of the file is downloaded and a file handle of the local file is stored. Every subsequent file IO operation is done locally. Only a flush or close operation will update the file on the remote server.
 * \param path Virtual path of the file
 * \param fi FUSE file information structure, which contains the handle of the file
 * \return Error code, or 0 if everything went fine
 */
int ngfs_open(const char *path,struct fuse_file_info *fi) {
#ifdef TRACE
	fprintf(stderr,"ngfs_open(%s)\n",path);
#endif
	// First check if file is already in cache
	int fd;
	FileList *fl;
	pthread_mutex_lock(&cache_lock);
	fl=find_name(cache,path);
	if (fl!=0) {	// File is already in cache, just open it
		char name[200];
		get_local_file_name(fl->file.id,name);
		fd=open(name,O_RDWR);	
		fl->file.handle=fd;
		pthread_mutex_unlock(&cache_lock);
		if (fd==0) return -1;
	} else {
		pthread_mutex_unlock(&cache_lock);
		char parent[PATH_SIZE];
		char name[PATH_SIZE];
		get_path_and_name(path,parent,name);
		Folder *par;	// Parent folder
		Folder *cur;	// Current file
		// Check if file can be opened
		int code=get_node(parent,&par);
		if (code!=0 || par==0) return -ENOTDIR;	// Bad path
		cur=find_item(par->elements,name);
		if (cur==0 && (fi->flags & O_WRONLY)==0 && (fi->flags & O_RDWR)==0) return -ENOENT;	// A readonly access was required but the file does not exist
		if (cur!=0 && (fi->flags & O_CREAT)!=0) return -EEXIST;	// A create command was issued but the file already exists
		if (cur!=0 && cur->file->type==T_FOLDER && ((fi->flags & O_WRONLY)!=0 || (fi->flags & O_RDWR)!=0)) return -EISDIR;	// A write access was required but the file is a folder
		// Create associated temporary file, download it if necessary
		pthread_mutex_lock(&cache_lock);
		char localname[200];
		get_local_file_name(cache_id,localname);
		if ((fi->flags & O_WRONLY)==0) {
			if (download_file(path,localname)!=0) {pthread_mutex_unlock(&cache_lock);return -1;}
		} 
		fd=open(localname,O_RDWR);
		if (fd==0) {pthread_mutex_unlock(&cache_lock);return -1;}
		// Add the file to the cache
		fl=create_file_list(fd,path,cur);
		fl->next=cache;
		cache=fl;
		pthread_mutex_unlock(&cache_lock);
	}
	// Set the FUSE file info structure
	fi->fh=(long)fl;	// Pointer to the file handle
	fi->direct_io=1;	// Needed because the file size returned by ngfs_stat is not accurate. This causes the read function to wait for a 0 byte response to detect the end of the file
	return 0;
}

/**
 * \brief Read from a virtual file
 *
 * This function reads size bytes from a virtual file into the buffer buf, beginning offset bytes into the file. It returns the actual number of bytes transferred, or 0 if offset was at or beyond the end of the file.
 * \param path Virtual path of the file on the remote server
 * \param buf Buffer used to store the bunch of bytes read
 * \param size Number of bytes which should be read
 * \param offset Offset relative to the start of the file, from which the bytes have to be read
 * \param fi FUSE file information, holding the file handle
 * \return Actual number of bytes transferred, or a negative value with an error code if an error occurred
 */
int ngfs_read(const char *path,char *buf,size_t size,off_t offset,struct fuse_file_info *fi) {
#ifdef TRACE
	fprintf(stderr,"ngfs_read(%s,%i,%i,%p)\n",path,(int)size,(int)offset,(void*)(long)(fi->fh));
	print_file_list(stderr,cache);
#endif
	if (fi==0) return -EBADF;
	pthread_mutex_lock(&cache_lock);
	FileList *fl=(FileList*)(long)(fi->fh);
	off_t code=lseek(fl->file.handle,offset,SEEK_SET);
	if (code==-1) {
		pthread_mutex_unlock(&cache_lock);
		return -1;
	}
	ssize_t num=read(fl->file.handle,buf,size);
	pthread_mutex_unlock(&cache_lock);
	if (num==-1) return -errno; else return num;
}

/**
 * \brief Write to a virtual file
 *
 * This function writes size bytes on the virtual file, from the buffer buf, starting offset bytes into the file. It returns the actual number of bytes written.
 * \param path Virtual path of the file on the remote server
 * \param buf Buffer used to store the bunch of bytes read
 * \param size Number of bytes which should be read
 * \param offset Offset relative to the start of the file, from which the bytes have to be read
 * \param fi FUSE file information, holding the file handle
 * \return Actual number of bytes transferred, or a negative value with an error code if an error occurred
 */
int ngfs_write(const char *path,const char *buf,size_t size,off_t offset,struct fuse_file_info *fi) {
#ifdef TRACE
	fprintf(stderr,"ngfs_write(%s,%i,%i,%p)\n",path,(int)size,(int)offset,(void*)(long)(fi->fh));
	print_file_list(stderr,cache);
#endif
	if (fi==0) return -EBADF;
	pthread_mutex_lock(&cache_lock);
	FileList *fl=(FileList*)(long)(fi->fh);
	off_t code=lseek(fl->file.handle,offset,SEEK_SET);
	if (code==-1) {
		pthread_mutex_unlock(&cache_lock);
		return -1;
	}
	ssize_t num=write(fl->file.handle,buf,size);
	pthread_mutex_unlock(&cache_lock);
	if (num==-1) return -errno; else return num;
}

/**
 * \brief Close a virtual file
 *
 * This function closes a virtual file. If it has been modified, the new version is copied to the remote server. The temporary file on the local computer is deleted and all resources allocated are released.
 * \param path Virtual path of the file
 * \param fi FUSE file information structure, holding the file handle and file access mode
 * \return Error code, or 0 if everything went fine
 */
int ngfs_release(const char *path,struct fuse_file_info *fi) {
#ifdef TRACE
	fprintf(stderr,"ngfs_close(%s,%p)\n",path,(void*)(long)(fi->fh));
	print_file_list(stderr,cache);
#endif
	if (fi==0) return -EBADF;
	pthread_mutex_lock(&cache_lock);
	FileList *fl=(FileList*)(long)(fi->fh);
	struct stat statbuf;
	int code=fstat(fl->file.handle,&statbuf);
	if (code!=0) {
		pthread_mutex_unlock(&cache_lock);
		return -1;
	}
	close(fl->file.handle);
	fl->file.handle=0;
	if ((fi->flags & O_WRONLY)!=0 || (fi->flags & O_RDWR)!=0) {	// File was writable, so upload the new version on the server
		char pathname[PATH_SIZE];
		char filename[PATH_SIZE];
		get_path_and_name(fl->file.path,pathname,filename);
		if (fl->file.folder!=0) delete_file(fl->file.path);
		char name[200];
		get_local_file_name(fl->file.id,name);
		code=upload_file(pathname,filename,name);
		if (code!=0) {
			pthread_mutex_unlock(&cache_lock);
			return -1;
		}
	}
	//unlink(fl->file.localpath);	// The file is not deleted and kept in cache in case of future access
	fl->file.handle=0;
	cache_size+=statbuf.st_size;
	if (cache_size>CACHE_MAX_SIZE) {	// If cache is full, clean it up by removing all old files
		cleanup_cache();
		cache_size=0;
	}
	//free_handle(&cache,(FileList*)(long)(fi->fh));
	//cache_number--;
	pthread_mutex_unlock(&cache_lock);
	return 0;
}

/**
 * \brief Resync the cache file with the remote file
 *
 * This function updates the file on the remote server so that is is the same as the local one in the cache. It actually writes down all modification to the remote server.
 * \param path Virtual path of the file
 * \param isdatasync Indicate if data have to be updated, or only metadata. This parameter is not implemented in the filesystem.
 * \param fi FUSE file information structure, holding the file handle and file access mode
 * \return Error code, or 0 if everything went fine
 */
int ngfs_fsync(const char *path,int isdatasync,struct fuse_file_info *fi) {
#ifdef TRACE
	fprintf(stderr,"ngfs_fsync(%s,%p)\n",path,(void*)(long)(fi->fh));
	print_file_list(stderr,cache);
#endif
	if (fi==0) return -EBADF;
	pthread_mutex_lock(&cache_lock);
	FileList *fl=(FileList*)(long)(fi->fh);
	int code;
	fsync(fl->file.handle);
	if ((fi->flags & O_WRONLY)!=0 || (fi->flags & O_RDWR)!=0) {	// File was writable, so upload the new version on the server
		char pathname[PATH_SIZE];
		char filename[PATH_SIZE];
		get_path_and_name(fl->file.path,pathname,filename);
		code=delete_file(fl->file.path);
		if (code!=0) {
			pthread_mutex_unlock(&cache_lock);
			return -1;
		}
		char name[200];
		get_local_file_name(fl->file.id,name);
		code=upload_file(pathname,filename,name);
		if (code!=0) {
			pthread_mutex_unlock(&cache_lock);
			return -1;
		}
	}
	pthread_mutex_unlock(&cache_lock);
	return 0;
}

/**
 * \brief Get general information about the filesystem itself
 *
 * This function retrieves general information about the filesystem. It is needed to get the free space of the filesystem, as returned by df command.
 * \param path Virtual path, ignored
 * \param stbuf Structure in which the information about the filesystem will be stored
 * \return Error code, or 0 if everything went fine
 */
int ngfs_statfs(const char *path,struct statvfs *stbuf) {
#ifdef TRACE
	fprintf(stderr,"ngfs_statfs\n");
#endif
	stbuf->f_bsize=0x400;
	stbuf->f_frsize=1;
	stbuf->f_blocks=9*(1 << 20);
	Folder *fold;
	get_node("/",&fold);
	fold=fold->elements;
	ssize_t total=0;
	while (fold!=0) {
		total+=fold->file->size;
		fold=fold->next;
	}
	stbuf->f_bfree=stbuf->f_blocks-total/0x400;
	stbuf->f_bavail=stbuf->f_bfree;
	stbuf->f_files=0;
	stbuf->f_ffree=0;
	stbuf->f_favail=0;
	stbuf->f_namemax=PATH_SIZE-1;
	return 0;
}

/**
 * \brief Create a file
 *
 * This function creates a file on the remote server. Actually, although it would be possible to create a 0-byte file on the remote server, this operation would be too time-consuming. So the file is only created locally. This may result in unexpected behaviour if another process tries to create a file while the previous one is modified.
 * \param path Virtual path of the file on the remote server
 * \param mode Mode of the file opened
 * \param fi FUSE file information structure, which will get the new file handle
 * \return Error code, or 0 if everything went fine
 */
int ngfs_create(const char *path,mode_t mode,struct fuse_file_info *fi) {
#ifdef TRACE
	fprintf(stderr,"ngfs_create(%s,%o)\n",path,(int)mode);
#endif
	char parent[PATH_SIZE];
	char name[PATH_SIZE];
	get_path_and_name(path,parent,name);
	Folder *par;	// Parent folder
	Folder *cur;	// Current file
	// Check if file can be opened
	int code=get_node(parent,&par);
	if (code!=0 || par==0) return -ENOTDIR;	// Bad path
	cur=find_item(par->elements,name);
	if (cur!=0 && (fi->flags & O_CREAT)!=0) return -EEXIST;	// A create command was issued but the file already exists
	// Create associated temporary file, download it if necessary
	pthread_mutex_lock(&cache_lock);
	int fd;
	char localname[200];
	get_local_file_name(cache_id,localname);
	fd=open(localname,O_RDWR | O_CREAT,0666);
	if (fd==0) {pthread_mutex_unlock(&cache_lock);return -1;}
	// Add the file to the cache
	FileList *fl=create_file_list(fd,path,0);
	fl->next=cache;
	cache=fl;
	// Set the FUSE file info structure
	fi->fh=(long)fl;	// Pointer to the file handle
	fi->direct_io=1;	// Needed because the file size returned by ngfs_stat is not accurate. This causes the read function to wait for a 0 byte response to detect the end of the file
	pthread_mutex_unlock(&cache_lock);
	return 0;
}

/**
 * \brief Flush a file
 *
 * This function is called on each close so that the filesystem has a chance to report delayed errors.
 * \param path Virtual path of the file on the remote server
 * \param fi FUSE file information structure, which will get the new file handle
 * \return Error code, or 0 if everything went fine
 */
int ngfs_flush(const char *path,struct fuse_file_info *fi) {
#ifdef TRACE
	fprintf(stderr,"ngfs_flush(%s,%p)\n",path,(void*)(long)(fi->fh));
#endif
	return 0;
}
 
/**
 * \brief List of callback functions
 *
 * This constant holds the references of the callback functions used when an operation is made on a file on the FUSE filesystem.
 */
struct fuse_operations ngfs_oper = {
	.init=ngfs_init,
	.getattr=ngfs_getattr,
	.fgetattr=ngfs_fgetattr,
	.access=ngfs_access,
	.opendir=ngfs_opendir,
	.releasedir=ngfs_releasedir,
	.readdir=ngfs_readdir,
	.mkdir=ngfs_mkdir,
	.unlink=ngfs_unlink,
	.rmdir=ngfs_rmdir,
	.rename=ngfs_rename,
	.truncate=ngfs_truncate,
	.ftruncate=ngfs_ftruncate,
	.statfs=ngfs_statfs,
	.open=ngfs_open,
	.read=ngfs_read,
	.write=ngfs_write,
	.release=ngfs_release,
	.fsync=ngfs_fsync,
	.statfs=ngfs_statfs,
	.create=ngfs_create,
	.flush=ngfs_flush,
	.destroy=ngfs_destroy
};

/**
 * \brief Main loop
 *
 * This function starts the program and calls the main loop
 * \param argc Number of arguments on the command line, including the name of the program
 * \param argv List of arguments on the command line
 * \return Error code of the program, 0 if everything went fine
 */
int main(int argc,char **argv) {
	// Get system information
	uid=geteuid();
	gid=getegid();
	// Parse command line arguments
	struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
	struct fuse_opt opts[]={
		NGFS_OPT_KEY2("-u %s","--user=%s",username,-1),
		NGFS_OPT_KEY2("-p %s","--password=%s",password,-1),
		FUSE_OPT_END
	};
	memset(&options,0,sizeof(struct options));
	if (fuse_opt_parse(&args,&options,opts,0)==-1) return 1;
	// Daemonize the program
	int code=fuse_main(args.argc,args.argv,&ngfs_oper,0);
	fuse_opt_free_args(&args);
	return code;
}
